/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 d3leaf@126.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.mapperdb.db;

import java.util.List;

import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.SelectKey;
import org.apache.ibatis.mapping.ResultFlag;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandler;

import com.mapperdb.core.Assert;
import com.mapperdb.helper.AnntHandler;
import com.mapperdb.joor.Reflect;
import com.mapperdb.util.MapperUtils;

public class ResultEntity {
	public static final Result[] EMPTY_RESULTS = new Result[0];
	protected Class<?> entityClass;
	protected Result[] results;
	protected SelectKey selectKey;
	protected String resultMapId;
	protected String name;

	public ResultEntity() {}

	ResultEntity(ResultEntity resultEntity) {
		this.entityClass = resultEntity.entityClass;
		this.results = resultEntity.results;
		this.selectKey = resultEntity.selectKey;
		this.resultMapId = resultEntity.resultMapId;
		this.name = resultEntity.name;
	}

	public ResultEntity(Class<?> entityClass) {
		this(new Builder().entityClass(entityClass).build());
	}

	public ResultEntity(Class<?> entityClass, Class<? extends AnntConfig> anntClass) {
		this(new Builder().entityClass(entityClass)//
				.anntClass(anntClass)//
				.build());
	}

	public ResultEntity(Class<?> entityClass, SelectKey selectKey, String resultMapId, String name) {
		this(new Builder().entityClass(entityClass)//
				.selectKey(selectKey)//
				.resultMapId(resultMapId)//
				.tableName(name)//
				.build());
	}

	public SelectKey selectKey() {
		return selectKey;
	}

	public String tableName() {
		return name;
	}

	public Result[] results() {
		return results;
	}

	public Class<?> entityClass() {
		return entityClass;
	}

	public String resultMapId() {
		return resultMapId;
	}

	public boolean isKey(Result column, SelectKey selectKey) {
		if (!column.id()) return false;
		if (selectKey == null) return true;

		if (column.property().equals(selectKey.keyProperty()) //
				&& !selectKey.before() //
				&& isNestedSelect(column)) { return true; }
		return false;
	}

	public String getInsertColumns() {
		StringBuffer sb = new StringBuffer();
		boolean flag = false;
		for (Result result : results) {
			if (isKey(result, selectKey)) continue;
			if (flag) sb.append(" , ");

			flag = true;
			sb.append(result.column());
		}
		return sb.toString();
	}

	public String getSelectColumns() {
		StringBuffer sb = new StringBuffer();
		boolean flag = true;
		for (Result result : results) {
			if (hasNestedSelect(result)) continue;
			if (!flag) sb.append(" , ");
			sb.append(result.column());
			flag = false;

		}
		return sb.toString();
	}

	public String getAsSelectColumns() {
		StringBuffer sb = new StringBuffer();
		boolean flag = true;
		for (Result result : results) {
			if (hasNestedSelect(result)) continue;
			if (!flag) sb.append(" , ");
			sb.append(result.column()).append(" as ").append(result.property());
			flag = false;

		}
		return sb.toString();
	}

	protected boolean hasNestedSelect(Result result) {
		return result.one().select().length() > 0 || result.many().select().length() > 0;
	}

	protected boolean isNestedSelect(Result result) {
		return result.one().select().length() == 0 && result.many().select().length() == 0;
	}

	public static class Builder {
		ResultEntity table = new ResultEntity();
		Class<? extends AnntConfig> anntClass;
		private Configuration configuration;

		public Builder entityClass(Class<?> entityClass) {
			table.entityClass = entityClass;
			return this;
		}

		public Builder results(Result[] results) {
			table.results = results;
			return this;
		}

		public Builder selectKey(SelectKey selectKey) {
			table.selectKey = selectKey;
			return this;
		}

		public Builder resultMapId(String resultMapId) {
			table.resultMapId = resultMapId;
			return this;
		}

		public Builder configuration(Configuration configuration) {
			this.configuration = configuration;
			return this;
		}

		public Builder tableName(String tableName) {
			table.name = tableName;
			return this;
		}

		public Builder anntClass(Class<? extends AnntConfig> anntClass) {
			this.anntClass = anntClass;
			return this;
		}

		public ResultEntity build() {
			if (table.results != null) {

			} else if (table.resultMapId == null && anntClass == null) {
				table.selectKey = MapperUtils.parseSelectKey(table.entityClass);
				table.results = MapperUtils.getResults(table.entityClass);
				table.name = MapperUtils.getTableName(table.entityClass);
			} else if (anntClass != null) {
				Reflect anntConfigReflect = Reflect.on(anntClass).cacheMethod("config");
				table.entityClass = table.entityClass;
				table.selectKey = anntConfigReflect.annt(SelectKey.class).get();
				table.results = ((Results) anntConfigReflect.annt(Results.class).get()).value();
				table.name = MapperUtils.getTableName(anntClass);
			} else if (table.resultMapId != null) {
				table.results = buildResults(this.configuration, table.resultMapId);
			}
			Assert.hasLength(table.name, "Table name can not be blank.");
			return table;
		}
	}

	static Result[] buildResults(Configuration configuration, String resultMapId) {
		ResultMap resultMap = configuration.getResultMap(resultMapId);
		List<ResultMapping> resultMappings = resultMap.getResultMappings();
		Result[] results = new Result[resultMappings.size()];

		ResultMapping temp = null;

		for (int i = 0; i < results.length; i++) {
			temp = resultMappings.get(i);
			results[i] = AnntHandler.use(Result.class)//
					.set("id", isId(temp))//
					.set("column", temp.getColumn())//
					.set("property", temp.getProperty())//
					.set("javaType", temp.getJavaType(), false)//
					.set("jdbcType", temp.getJdbcType(), false)//
					.set("typeHandler", resolveTypeHandler(temp.getTypeHandler()), false)//
					.newProxyInstance();
		}
		return results;
	}

	@SuppressWarnings("unchecked")
	protected static Class<TypeHandler<?>> resolveTypeHandler(TypeHandler<?> typeHandler) {
		if (typeHandler == null) return null;
		return (Class<TypeHandler<?>>) typeHandler.getClass();
	}

	public static boolean isId(ResultMapping mapping) {
		List<ResultFlag> flags = mapping.getFlags();
		if (flags.size() > 0 && flags.get(0) == ResultFlag.ID) return true;
		return false;
	}
}
